//
//  APIsParser.swift
//  MacDemo
//
//  Created by FH on 2020/11/13.
//  Copyright © 2020 FH. All rights reserved.
//

import Foundation

fileprivate func isComplexType(type: String) -> Bool {
    return type.starts(with: "id<")
        || type.starts(with: "NSArray<") || type.starts(with: "NSMutableArray<")
        || type.starts(with: "NSDictionary<") || type.starts(with: "NSMutableDictionary<")
        || type.starts(with: "NSSet<") || type.starts(with: "NSMutableSet<")
}

fileprivate let complexTypeRegex = try! Regex(pattern: "(.+?)<(.+?)(\\s+\\*)?(\\s*,\\s*(.+?)(\\s+\\*)?)?>",
                                              groupNames: "container", "type", "typeRef", "dicTypeInfo", "dicType")
fileprivate func getComplexInnerType(_ typeStr: String) -> (container: String, type: String) {
    let match = complexTypeRegex.findFirst(in: typeStr)!
    let container = match.group(named: "container")!
    let type = match.group(named: "type")!
    if let dicType = match.group(named: "dicType") {
        return (container: container, type: dicType)
    } else {
        return (container: container, type: type)
    }
}

fileprivate func customTypeMapping(type: String, forDeclType: Bool) -> String {
    if type == "COLOR_CLASS" || type == "COLOR_CLASS *" {
    #if os(OSX)
        return forDeclType ? "NSColor" : "NSColor *"
    #else
        return forDeclType ? "UIColor" : "UIColor *"
    #endif
    } else if type == "VIEW_CLASS" || type == "VIEW_CLASS *" {
    #if os(OSX)
        return forDeclType ? "NSView" : "NSView *"
    #else
        return forDeclType ? "UIView" : "UIView *"
    #endif
    }
    return type
}

enum AstNodeType : Int, Codable {
    case blank
    case comment
    case enumerate
    case interface
    case method
    case block
    case parameter
    case property
    case `protocol`
    case namespace
    case `class`
    case function
    
    init?(enumCase: Int) {
        if enumCase == 4 {
            self = .protocol
        } else if enumCase == 3 {
            self = .interface
        } else if enumCase == 2 {
            self = .enumerate
        } else if enumCase == 1 {
            self = .method
        }
        return nil
    }
    
    static func toList() -> [String] {
        return ["All", "method", "enumerate", "interface", "protocol"]
    }
}

class AstNodeValue : Codable {
    var isValueNonnull = false
    var isValueDelegate = false
    var isValueBlock = false
    var isStaticValue = false
    var isValueComplexType = false
    var valueComplexContainerType: String?
    var valueComplexInnerType: String?
    var valueType: String? {
        didSet {
            if let newValue = self.valueType {
                valueType = customTypeMapping(type: newValue, forDeclType: false)
                self.isValueComplexType = isComplexType(type: valueType ?? "")
                if self.isValueComplexType {
                    let complex = getComplexInnerType(valueType!)
                    self.valueComplexContainerType = complex.container
                    if complex.container == "id" {
                        self.isValueDelegate = true
                    }
                    self.valueComplexInnerType = complex.type
                }
            }
        }
    }
    var rawType: String?
    var typeEncoding: String?
    var value: String?
    var valueRange: String?
    var valueValidate: String?
    var blockSignature: AstNode?
}

extension AstNodeValue : CustomDebugStringConvertible {
    var debugDescription: String {
        return "type: \(valueType ?? ""), value is \(value ?? "")"
    }
    
    var isObjCRefType : Bool {
        return self.typeEncoding!.hasPrefix("@")
    }
    
    var isReturnVoid : Bool {
        return self.valueType == "void"
    }
    
    var isTypealiasType : Bool {
        return (self.rawType ?? "").count > 0
    }
    
    var intHexValue : String? {
        return formatIntHex(value: self.value, rawType: self.valueType!)
    }
}

class AstNode : Codable {
    var order: Int = 0
    let isMainNode: Bool
    let type: AstNodeType
    var name: String
    var parentNodeType: AstNodeType?
    var parentName: String?
    var comment: String?
    var isDeclComplexType = false
    var declComplexContainerType: String?
    var declComplexInnerType: String?
    var declType: String? {
        didSet {
            if let newValue = self.declType {
                declType = customTypeMapping(type: newValue, forDeclType: true)
                self.isDeclComplexType = isComplexType(type: declType ?? "")
                if self.isDeclComplexType {
                    let complex = getComplexInnerType(declType!)
                    self.declComplexContainerType = complex.container
                    self.declComplexInnerType = complex.type
                }
            }
        }
    }
    var isDeprecated = false {
        didSet {
            if self.isDeprecated && self.type == .method {
                self.addTag(type: .available, tag: AstNodeTagAvailable.deprecated)
            }
        }
    }
    var deprecatedMsg: String?
    var value: AstNodeValue?
    var tags: [String : String] = [:]
    var children: [AstNode] = []
    
    init(type: AstNodeType, name: String) {
        self.type = type
        self.name = name
        if type == .enumerate || type == .interface || type == .protocol || type == .class || type == .function {
            isMainNode = true
        } else {
            isMainNode = false
        }
    }
    
    func splitSelector() -> [String] {
        return self.name.components(separatedBy: ":").reversed()
    }
}

extension AstNode : Hashable {
    static func == (lhs: AstNode, rhs: AstNode) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(self.name)
        if self.parentName != nil {
            hasher.combine(self.parentName!)
            hasher.combine(self.parentNodeType!)
        }
        if let valueNode = self.value, let value = valueNode.value {
            hasher.combine(value)
        }
    }
}

extension AstNode : CustomDebugStringConvertible {
    var debugDescription: String {
        if let value = self.value {
            return "[\(order)]node type is \(type), name: \(name), valueInfo: \(value)"
        } else {
            return "[\(order)]node type is \(type), name: \(name)"
        }
    }
}

extension AstNode {
    var isInitMethod: Bool {
        if let value = self.value, self.type == .method {
            if value.isStaticValue {
                if let parentName = self.parentName {
                    return parentName == self.declType!
                }
            } else {
                return self.name.hasPrefix("init")
            }
        }
        return false
    }
}

/////////////////////////////////////////////////////////////////

class APIsParser {
    private let regex = try! Regex(pattern:">\\s*((line:\\d+:\\d+)|(col:\\d+))\\s+(.+?)(\\s+'(.+?)'(:'.+?')?)?\\n",
                                   groupNames:"source", "line", "col", "name", "typeInfo", "type")
    // TODO: remove
    private let cppRegex = try! Regex(pattern: ">\\s*(line:\\d+:\\d+)\\s+(.+?\\s+)?(.+?)\\n",
                                      groupNames: "line", "symbol", "name")
    private let enumItemValueRegex = try! Regex(pattern: ">\\s+'(.+?)'\\s+(\\d+)\\n",
                                                groupNames: "type", "value")
    private let enumItemConstantRegex = try! Regex(pattern: #"\s*EnumConstant\s*.+?'(.+?)'\s+'(.+?)'.+?\n"#,
                                                   groupNames: ["constantCase", "type"])
    private let deprecatedAttrRegex = try! Regex(pattern: ">\\s+\"(.+?)\"\\s+",
                                                 groupNames: "comment")
    private let commentTextRegex = try! Regex(pattern: "Text=\"(.+?)\"\\n",
                                              groupNames: "comment")
    private let propertyRegex = try! Regex(pattern: "\\s*col:\\d+\\s+(.+?)\\s+'(.+?)(\\s+_.+?)?'",
                                           groupNames: "name", "type", "extendInfo")
    private let methodRegex = try! Regex(
        pattern: ">\\s*((col:\\d+)|(line:\\d+:\\d+))\\s+(\\+|-)\\s+(.+?)(\\s+'(.+?)')(:\\s*'(.+?)')?\\n",
        groupNames: "source", "col", "line", "belong", "name", "returnInfo", "returnDetail", "returnTypeInfo", "returnType")
    private let returnTypeRegex = try! Regex(pattern: "'(.+?)\\s+_(.+?)'",
                                             groupNames: "type", "extendInfo")
    private let paramsRegex = try! Regex(pattern: ">\\s*col:\\d+\\s+(.+?)\\s+'(.+?)(\\s+_(.+?))?'(:\\s*?'(.+?)')?\\n",
                                         groupNames: "name", "type", "extendInfo", "extend", "detailTypeInfo", "detailType")
    private let blockTypeRegex = try! Regex(pattern: ">\\scol:\\d+\\s+(.+?)\\s+'(.+?)\\s+\\((\\^.+?)\\)\\((.*?)\\)'(:.+?)?\\n",
                                            groupNames: "name", "returnType", "blockType", "params", "signature")
    // -ObjCInterface 0x110c68158 \'AgoraRtcEngineKit\'\n"
    private let categoryInterfaceRegex = try! Regex(pattern: "\\s+'(.+?)'\\n",
                                                    groupNames: ["name"])
    // parse source files
    private let clangAstReader: LineReader!
    private let jsonFilePath: String!
    // use for parsing objc
    private var keyWord: String = ""
    // use for parsing cpp
    private var keyNodeTypes: [AstNodeType] = []
    
    
    let debugCheckTypes = false
    var allParams = Set<String>()
    
    init(keyWord: String, dumpFilePath: String?=nil, jsonFilePath: String?=nil) {
        assert(keyWord.count > 0, "parser need keyWords count > 0")
        self.jsonFilePath = jsonFilePath
        self.keyWord = keyWord
        self.clangAstReader = self.jsonFilePath == nil ? LineReader(path: dumpFilePath!) : nil
    }
    
    static func getMainNode(stack: Stack<AstNode>,
                            nodes: inout [String : AstNode],
                            findMainNodeType: AstNodeType?=nil) -> AstNode? {
        while let top = stack.top() {
            if !top.isMainNode {
                stack.pop()
            } else // for cpp subClass && subEnum in class
                if let parentType = top.parentNodeType, parentType == .class {
                stack.pop()
            } else {
                if let findType = findMainNodeType, findType != top.type {
                    Self.resetStack(stack, currNode: top, nodes: &nodes)
                } else {
                    return top
                }
            }
        }
        return nil
    }
    
    private static func resetStack(_ stack: Stack<AstNode>, currNode: AstNode?, nodes: inout [String : AstNode]) {
        Self.saveToNodes(currNode, nodes: &nodes)
        stack.pop()
    }
    
    private static func saveToNodes(_ currNode: AstNode?, nodes: inout [String : AstNode]) {
        if let node = currNode, node.isMainNode {
            var needSave = true
            if let parentType = node.parentNodeType, parentType == .class {
                needSave = false
            }
            if needSave {
                let nodeName = Self.getSaveNodeName(node)
                if let preNode = nodes[nodeName] {
                    if preNode.children.count == 0 && node.children.count > 0 {
                        nodes[nodeName] = node
                    }
                } else {
                    if node.type == .enumerate && node.declType == nil {
                        // for CPP parser
                        if let caseNode = node.children.first, let valueType = caseNode.declType {
                            node.declType = valueType
                        }
                    }
                    nodes[nodeName] = node
                }
            }
        }
    }
    
    static func startParseMainNode(stack: Stack<AstNode>, currNode: inout AstNode?, nodes: inout [String : AstNode]) {
        if !stack.isEmpty {
            if stack.count > 1 {
                currNode = Self.getMainNode(stack: stack, nodes: &nodes)
            }
            Self.resetStack(stack, currNode: currNode, nodes: &nodes)
        }
    }
    
    static func handleLastNode(_ lastNode: AstNode?, stack: Stack<AstNode>, nodes: inout [String : AstNode]) {
        var currNode: AstNode! = lastNode
        if let node = lastNode {
            var nodeName = Self.getSaveNodeName(node)
            if !node.isMainNode
                || (node.parentNodeType != nil && node.parentNodeType! == .class),
                let topNode = Self.getMainNode(stack: stack, nodes: &nodes) {
                nodeName = Self.getSaveNodeName(topNode)
                currNode = topNode
            }
            if nodes[nodeName] == nil {
                nodes[nodeName] = currNode
            }
       }
    }
    
    static private func getSaveNodeName(_ node: AstNode) -> String {
        // for cpp
        if let parentNodeType = node.parentNodeType, parentNodeType == .namespace {
            let nodeName = "\(node.parentName!)::\(node.name)"
            if node.type == .enumerate {
                return node.name.starts(with: node.parentName!) ? node.name : nodeName
            } else {
                return nodeName
            }
        }
        return node.name
    }
    
    func parse() -> [String : AstNode] {
        if let jsonFilePath = self.jsonFilePath,
            let data = try? Data(contentsOf: URL(fileURLWithPath: jsonFilePath)),
            let nodes = try? JSONDecoder().decode([String: AstNode].self , from: data) {
            // set methods & properties index
            for (_, node) in nodes where node.type == .interface || node.type == .protocol {
                var methodIndex = 1
                var propertyIndex = 1
                for subNode in node.children {
                    if subNode.type == .method {
                        subNode.order = methodIndex
                        methodIndex += 1
                    } else if subNode.type == .property {
                        subNode.order = propertyIndex
                        propertyIndex += 1
                    }
                }
            }
            return nodes
        }
        
        var nodes = [String : AstNode]()
        if self.clangAstReader != nil {
            let stack = Stack<AstNode>()
            var currNode: AstNode? = nil
            for line in self.clangAstReader {
                autoreleasepool {
                    let checkObjCEnum = (line.starts(with: "|-EnumDecl") || line.starts(with: "`-EnumDecl")) && (line.contains(self.keyWord))
                    let checkCppEnum = line.contains("EnumDecl") && keyNodeTypes.contains(.enumerate) && self.cppRegex ~= line
                    if checkCppEnum {
                        print("TODO: remove")
                    }
                    if checkObjCEnum || checkCppEnum {
                        Self.startParseMainNode(stack: stack, currNode: &currNode, nodes: &nodes)
                        let tmpNode = createNode(forCpp: checkCppEnum, line: line, type: .enumerate)
                        if tmpNode.type == .enumerate {
                            currNode = tmpNode
                            stack.push(item: currNode!)
                        }
                    } else if (line.starts(with: "|-ObjCInterfaceDecl") || line.starts(with: "`-ObjCInterfaceDecl"))
                        // WatermarkOptions is a special name, it doesn't start with Agora
                        && (line.contains(self.keyWord) || line.contains("WatermarkOptions")) {
                        Self.startParseMainNode(stack: stack, currNode: &currNode, nodes: &nodes)
                        currNode = createNode(line: line, type: .interface)
                        stack.push(item: currNode!)
                    } else if line.starts(with: "| |-ObjCInterface ") && line.contains(self.keyWord) {
                        if let name = categoryInterfaceRegex.findFirst(in: line)?.group(named: "name") {
                            if let node = nodes[name], node.type == .interface {
                                currNode = node
                            } else {
                                currNode = AstNode(type: .interface, name: name)
                            }
                        }
                    }
                    else if (line.starts(with: "|-ObjCProtocolDecl") || line.starts(with: "`-ObjCProtocolDecl"))
                        && line.contains(self.keyWord) {
                        Self.startParseMainNode(stack: stack, currNode: &currNode, nodes: &nodes)
                        currNode = createNode(line: line, type: .protocol)
                        stack.push(item: currNode!)
                    } else if currNode != nil && line.contains("-EnumConstantDecl") {
                        if !currNode!.isMainNode {
                            currNode = stack.peek(num: 1)
                        }
                        let subNode = createNode(line: line, type: .property, parentNodeType: currNode?.type, parentName: currNode?.name)
                        currNode?.children.append(subNode)
                        currNode = subNode
                        stack.push(item: subNode)
                    } else if let parentType = currNode?.parentNodeType, let parentName = currNode?.parentName,
                              parentType == .enumerate && (line.contains("-IntegerLiteral") || line.contains("-DeclRefExpr")) {
                        if line.contains("DeclRefExpr"), let match = enumItemConstantRegex.findFirst(in: line),
                           let constantCase = match.group(named: "constantCase") {
                            for node in stack.storage {
                                if node.name == parentName && node.type == .enumerate {
                                    let valueNode = AstNodeValue()
                                    valueNode.valueRange = constantCase
                                    currNode?.value = valueNode
                                    break
                                }
                            }
                        } else {
                            let value = parseLiteralValue(line: line)
                            currNode?.value = value
                        }
                    } else if let currNode = currNode, line.contains("-DeprecatedAttr") {
                        let comment = parseDeprecatedAttr(line: line)
                        if currNode.type == .interface, let lastChildNode = currNode.children.last,
                            lastChildNode.type == .property {
                            lastChildNode.isDeprecated = true
                            lastChildNode.deprecatedMsg = comment
                        } else {
                            currNode.isDeprecated = true
                            currNode.deprecatedMsg = comment
                        }
                    } else if var node = currNode, line.contains("-TextComment") {
                        var needHandle = false
                        if let parentType = node.parentNodeType, parentType == .enumerate {
                            needHandle = true
                        } else if line.contains("DEPRECATED") || node.isDeprecated {
                            needHandle = true
                            if node.type == .interface, let lastChildNode = node.children.last,
                                lastChildNode.type == .property {
                                node = lastChildNode
                            }
                        }
                        if needHandle {
                            let comment = parseCommentText(line: line)
                            if comment.count > 0 {
                                if comment.contains("DEPRECATED") {
                                    node.isDeprecated = true
                                } else if node.isDeprecated && node.deprecatedMsg == nil {
                                    node.deprecatedMsg = "DEPRECATED \(comment)"
                                } else if let preComment = node.comment {
                                    node.comment = "\(preComment) \(comment)"
                                } else {
                                    node.comment = comment
                                }
                            }
                        }
                    } else if let node = currNode, node.isMainNode && node.type == .interface
                        && line.contains("-ObjCPropertyDecl") {
                        let propertyNode = parseProperty(line: line)
                        propertyNode.parentNodeType = node.type
                        propertyNode.parentName = node.name
                        node.children.append(propertyNode)
                    } else if let node = currNode,
                        ((node.type == .interface || node.type == .protocol)
                            || (node.parentNodeType != nil && (node.parentNodeType! == .interface || node.parentNodeType! == .protocol)))
                        && line.contains("-ObjCMethodDecl") {
                        // 对于Model，在处理完init方法后，要过滤掉后续的属性设置方法
                        if line.contains("implicit") {
                            currNode = Self.getMainNode(stack: stack, nodes: &nodes)
                        } else {
                            if !currNode!.isMainNode {
                                currNode = stack.peek(num: 1)
                            }
                            let methodNode = parseMethod(line: line, parentName: currNode!.name)
                            methodNode.parentNodeType = currNode!.type
                            methodNode.parentName = currNode!.name
                            currNode!.children.append(methodNode)
                            currNode = methodNode
                            stack.push(item: methodNode)
                        }
                    } else if let node = currNode,
                        node.type == .method && line.contains("-ParmVarDecl")
                        && (node.parentNodeType! == .interface || node.parentNodeType! == .protocol) {
                        let paramNode = parseParams(line: line)
                        node.children.append(paramNode)
                    } else if currNode != nil && (line.starts(with: "|-") || line.starts(with: "`-")) {
                        if stack.count > 0 {
                            while let top = stack.top() {
                                if top.isMainNode && top.children.count > 0 {
                                    nodes[top.name] = top
                                }
                                stack.pop()
                            }
                            currNode = nil
                        }
                    }
                }
            }
            Self.handleLastNode(currNode, stack: stack, nodes: &nodes)
            parseMethodEncoding(nodes: nodes)
        }
        // handle enum's auto-increment case
        for (_, node) in nodes where node.type == .enumerate {
            var currValue = -1
            for item in node.children {
                if item.name == "AgoraVideoProfileDEFAULT" {
                    print()
                }
                if let value = item.value {
                    if value.value == nil, let constantCase = value.valueRange {
                        for caseItem in node.children where caseItem.name == constantCase {
                            if let constantCaseValue = caseItem.value {
                                value.isValueNonnull = constantCaseValue.isValueNonnull
                                value.valueType = constantCaseValue.valueType
                                value.value = constantCaseValue.value
                                value.valueRange = nil
                                break
                            }
                        }
                    }
                    currValue = Int(value.value!)!
                } else {
                    currValue += 1
                    let valueNode = AstNodeValue()
                    valueNode.isValueNonnull = true
                    valueNode.valueType = node.declType
                    valueNode.value = "\(currValue)"
                    item.value = valueNode
                }
            }
        }
        if debugCheckTypes {
            for type in allParams {
                if !nodes.contains(where: { $0.key == type }) {
                    print(">>> not found type is \(type)")
                }
            }
        }
        return nodes
    }
    
    private func createNode(forCpp: Bool=false,
                            line: String,
                            type: AstNodeType,
                            parentNodeType: AstNodeType?=nil,
                            parentName: String?=nil) -> AstNode {
        var match: Match!
        if forCpp {
            // TODO: remove
            match = self.cppRegex.findFirst(in: line)!
            if let name = match.group(named: "name"), let firstChar = name.first, !firstChar.isUppercase {
                return AstNode(type: .blank, name: "name")
            }
        } else {
            match = self.regex.findFirst(in: line)!
        }
        var name = match.group(named: "name")!
        if name.starts(with: "referenced ") {
            name = name.replacingOccurrences(of: "referenced ", with: "")
        }
        let node = AstNode(type: type, name: name)
        if let type = match.group(named: "type") {
            node.declType = type
        }
        if parentName != nil {
            node.parentNodeType = parentNodeType
            node.parentName = parentName
        }
        return node
    }
    
    private func parseLiteralValue(line: String) -> AstNodeValue {
        let match = self.enumItemValueRegex.findFirst(in: line)!
        let type = match.group(named: "type")!
        let value = match.group(named: "value")!

        let valueNode = AstNodeValue()
        valueNode.isValueNonnull = true
        valueNode.valueType = type
        valueNode.value = value
        return valueNode
    }
    
    private func parseDeprecatedAttr(line: String) -> String {
        let match = self.deprecatedAttrRegex.findFirst(in: line)!
        return match.group(named: "comment")!.trimmingCharacters(in: .whitespaces)
    }
    
    private func parseCommentText(line: String) -> String {
        let match = self.commentTextRegex.findFirst(in: line)!
        return match.group(named: "comment")!.trimmingCharacters(in: .whitespaces)
    }
    
    private func parseProperty(line: String) -> AstNode {
        let match = self.propertyRegex.findFirst(in: line)!
        let name = match.group(named: "name")!
        let type = match.group(named: "type")!
        var isNonnull = true
        
        let propertyNode = AstNode(type: .property, name: name)
        if type.last == "*" && !isComplexType(type: type) {
            propertyNode.declType = String(type.dropLast(2))
        } else {
            propertyNode.declType = type
        }
        if let extendInfo = match.group(named: "extendInfo") {
            isNonnull = extendInfo.contains("Nonnull")
        }
        propertyNode.value = AstNodeValue()
        propertyNode.value?.valueType = type
        propertyNode.value?.isValueNonnull = isNonnull
        
        if debugCheckTypes {
            if propertyNode.isDeclComplexType {
                allParams.insert(propertyNode.declComplexInnerType!)
            } else {
                allParams.insert(propertyNode.declType!)
            }
        }
        return propertyNode
    }
    
    private func parseMethod(line: String, parentName: String) -> AstNode {
        // TODO: parse return block type
        var match = self.methodRegex.findFirst(in: line)!
        let belong = match.group(named: "belong")!
        let name = match.group(named: "name")!
        let methodNode = AstNode(type: .method, name: name)
        methodNode.value = AstNodeValue()
        methodNode.value?.isValueNonnull = true
        methodNode.value?.isStaticValue = belong == "+"
        if var returnDetail = match.group(named: "returnDetail") {
            var returnInfo = match.group(named: "returnInfo")
            if isComplexType(type: returnDetail)
                && !(returnDetail.contains("Nullable") || returnDetail.contains("Nonnull")) {
                returnDetail.append(" _Nullable")
                returnInfo? = "'\(returnDetail)'"
            }
            let splits = returnDetail.components(separatedBy: " ")
            var parseType = splits[0]
            let isInstancetype = parseType == "instancetype"
            
            if splits.count > 3, let returnInfo = returnInfo {
                match = self.returnTypeRegex.findFirst(in: returnInfo)!
                parseType = match.group(named: "type")!
                let extendInfo = match.group(named: "extendInfo")!
                methodNode.value?.isValueNonnull = extendInfo != "Nullable"
            }
            methodNode.declType = isInstancetype ? parentName : parseType
            if splits.count == 3 {
                parseType += " *"
                methodNode.value?.isValueNonnull = !splits[2].contains("Nullable")
            } else if splits.count == 2 {
                methodNode.value?.isValueNonnull = !splits[1].contains("Nullable")
            }
            methodNode.value?.valueType = isInstancetype ? "\(parentName) *" : parseType
        } else if let type = match.group(named: "returnType") {
            methodNode.value?.valueType = type.trimmingCharacters(in: .whitespaces)
            methodNode.declType = methodNode.value?.valueType
        }

        if debugCheckTypes {
            if methodNode.isDeclComplexType {
                allParams.insert(methodNode.declComplexInnerType!)
            } else {
                allParams.insert(methodNode.declType!)
            }
        }
        return methodNode
    }
    
    private func parseParams(line: String) -> AstNode {
        var paramNode: AstNode!
        if line.contains(" (^") {
            let match = self.blockTypeRegex.findFirst(in: line)!
            let name = match.group(named: "name")!
            let returnType = match.group(named: "returnType")!
            let blockType = match.group(named: "blockType")!
            let params = match.group(named: "params")!
            let splits = params.components(separatedBy: ",")
            paramNode = AstNode(type: .parameter, name: name)
            paramNode.declType = "__block"
            let valueInfo = AstNodeValue()
            valueInfo.valueType = "__block"
            valueInfo.typeEncoding = "@?"
            valueInfo.isValueBlock = true
            valueInfo.isValueNonnull = blockType.contains("Nonnull")
            // block signature
            let signature = AstNode(type: .block, name: name)
            // returnType
            let returnValue = AstNodeValue()
            returnValue.valueType = returnType
            signature.value = returnValue
            signature.declType = returnType
            // parameters
            var pos = 0
            for item in splits {
                let isNullable = item.contains("Nullable")
                let paramStr = item.replacingOccurrences(of: "_Nonnull", with: "")
                    .replacingOccurrences(of: "_Nullable", with: "")
                    .trimmingCharacters(in: .whitespaces)
                let itemSplits = item.trimmingCharacters(in: .whitespaces).components(separatedBy: " ")
                let paramType = itemSplits.first!
                let blockParam = AstNode(type: .parameter, name: "#\(pos)")
                blockParam.declType = paramType
                let paramValue = AstNodeValue()
                paramValue.isValueNonnull = !isNullable
                paramValue.valueType = paramStr
                blockParam.value = paramValue
                signature.children.append(blockParam)
                pos += 1
            }
            valueInfo.blockSignature = signature
            paramNode.value = valueInfo
        } else {
            let match = self.paramsRegex.findFirst(in: line)!
            let name = match.group(named: "name")!
            let type = match.group(named: "type")!
            paramNode = AstNode(type: .parameter, name: name)
            paramNode.value = AstNodeValue()
            paramNode.value?.valueType = type
            paramNode.value?.isValueNonnull = true
            if let extend = match.group(named: "extend") {
                paramNode.value?.isValueNonnull = extend.contains("Nonnull")
            }
            let splits = type.components(separatedBy: " ")
            if splits.count == 2 {
                paramNode.declType = splits[0]
            } else {
                paramNode.declType = type
            }
        }
        if debugCheckTypes {
            if paramNode.isDeclComplexType {
                allParams.insert(paramNode.declComplexInnerType!)
            } else {
                allParams.insert(paramNode.declType!)
            }
        }
        return paramNode
    }
    
    private func parseMethodEncoding(nodes: [String : AstNode]) {
        for node in nodes.values where node.type == .interface || node.type == .protocol {
            for subNode in node.children where subNode.type == .method || subNode.type == .property {
                _parseMethodEncoding(forBlock: false, method: subNode, allNodes: nodes)
            }
        }
    }
    
    private func _parseMethodEncoding(forBlock: Bool, method: AstNode, allNodes: [String: AstNode]) {
        var methodType = "@:"
        var rawType: String = ""
        let returnEncoding = typeEncoding(declType: method.declType!,
                                          typeStr: method.value!.valueType!,
                                          isComplexType: method.isDeclComplexType,
                                          realType: &rawType,
                                          allNodes)
        methodType = "\(returnEncoding)\(methodType)"
        // add block `sender` parameter typeEncoding
        if forBlock {
            methodType = "\(methodType)@"
        }
        
        method.value?.rawType = rawType
        for property in method.children {
            if property.value?.isValueBlock ?? false {
                if let blockSignature = property.value?.blockSignature {
                    _parseMethodEncoding(forBlock: true, method: blockSignature, allNodes: allNodes)
                    methodType = "\(methodType)\(property.value!.typeEncoding!)"
                    var blockSelector = "\(property.name)WithSender:"
                    for blockParam in blockSignature.children {
                        blockSelector += "\(blockParam.declType!):"
                    }
                    blockSignature.name = blockSelector
                }
            } else {
                let propertyEncoding = typeEncoding(declType: property.declType!,
                                                    typeStr: property.value!.valueType!,
                                                    isComplexType: property.isDeclComplexType,
                                                    realType: &rawType,
                                                    allNodes)
                property.value?.typeEncoding = propertyEncoding
                property.value?.rawType = rawType;
                methodType = "\(methodType)\(propertyEncoding)"
            }
        }
        method.value?.typeEncoding = methodType
    }
}
